#!/usr/bin/env python
# -*- coding: utf8 -*-
#-------------------------------------------------------------------------------
# Name:        xml_parser
# Purpose:      parse xml to show pull and push about yield
#
# Author:      iori
#
# Created:     02/11/2014
# Copyright:   (c) iori 2014
# Licence:     GPLv3
#-------------------------------------------------------------------------------

from itertools import chain
import unittest
import re

master_re = re.compile(r'(<[^>]*>)|([^< \t\n\r\f\v]+)')

def scanner(text):
    ''' scan a text and generate a sequence token
    '''
    for mo in master_re.finditer(text):
        token = mo.group()
        yield token

def is_open_tag(tag):
    return tag.startswith('<') and (not tag.startswith('</'))

def parser(text):
    ''' parse xml text '''
    def parse_items(close_tag=None):
        ''' <a> ... </a>'''
        res = []
        while True:
            item = yield  # <---
            if item == close_tag:
                break
            elif is_open_tag(item):
                elem = yield from parse_elem(item)
                res.append(elem)
            else:
                res.append(item)
        return res

    def parse_elem(open_tag):
        tag = open_tag[1:-1]
        items = yield from parse_items('</{}>'.format(tag)) # <--
        return (tag, items)

    stream_tokens = scanner(text)
    runner = parse_items()
    try:
        for token in chain([None], stream_tokens, [None]):
            runner.send(token)
    except StopIteration as e:
        return e.value

class TestMe(unittest.TestCase):
    def test_scanner(self):
        cases = [('<a> hello a-b thanks <b>good</b></a>',
                    ['<a>', 'hello', 'a-b', 'thanks', '<b>', 'good', '</b>','</a>'])]
        for text, res in cases:
            self.assertEqual(list(scanner(text)), res)

    def test_parser(self):
        cases = [('<a> hello a-b thanks <b>good</b></a>',
                    [('a', ['hello', 'a-b', 'thanks', ('b', ['good'])])])
                ]
        for text, res in cases:
            self.assertEqual(parser(text), res)

if __name__ == '__main__':
    unittest.main()
